﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace TestRabbitMQ
{

    public class RabbitMQConsumerApplication : IRabbitMQConsumerApplication
    {
        private ConnectionFactory _factory;
        private IConnection _connection;
        private List<Assembly> _assembly;
        private IServiceProvider _serviceProvider;
        private List<TypeToAttri> _queues;
        public RabbitMQConsumerApplication(IOptions<RabbitMQConfig> options, IServiceProvider serviceProvider)
        {
            var option = options.Value;
            //创建连接工厂
            _factory = new ConnectionFactory
            {
                UserName = option.UserName,//用户名
                Password = option.Password,//密码
                HostName = option.HostName,//rabbitmq ip
                AutomaticRecoveryEnabled = true,
                NetworkRecoveryInterval = TimeSpan.FromSeconds(10),//重连间隔时间
                TopologyRecoveryEnabled = true//重新声明交换机/队列等信息
            };

            //创建连接
            _connection = _factory.CreateConnection();

            _queues = option.Queues;
            _serviceProvider = serviceProvider;
        }

        public void Start()
        {
            _queues.ForEach(item =>
            {
                ConsumerGenerator(item.TypeInfo, item.Method, item.Queue.QueueName);
            });
        }

        /// <summary>
        /// 根据队列名称生成消费者
        /// </summary>
        /// <param name="type">执行类</param>
        /// <param name="methodname">执行方法</param>
        /// <param name="queueName">队列名称</param>
        void ConsumerGenerator(Type type, MethodInfo methodname, string queueName)
        {
            Task.Run(() =>
            {
                try
                {
                    //创建通道
                    var channel = _connection.CreateModel();

                    //声明一个队列:防止消费者启动失败
                    channel.QueueDeclare(queueName, false, false, false, null);

                    //事件基本消费者
                    EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
                    //接收到消息事件
                    consumer.Received += (ch, ea) =>
                    {
                        var message = Encoding.UTF8.GetString(ea.Body);
                        Console.WriteLine($"Queue:{queueName},收到消息： {message}");

                        var res = InvokeMethod(type, methodname, message);

                        Console.WriteLine($"消息是否消费：{res},Time:{DateTime.Now}");

                        //确认该消息已被消费
                        if (res) channel.BasicAck(ea.DeliveryTag, false);
                    };

                    //启动消费者 设置为手动应答消息
                    channel.BasicConsume(queueName, false, consumer);
                    Console.WriteLine($"Queue:{queueName}，消费者已启动");
                }
                catch (Exception ex)
                {

                    Console.WriteLine($"Queue Error:{ex.Message}");
                    throw;
                }
            });
        }

        /// <summary>
        /// 收到消息执行对应方法
        /// </summary>
        /// <param name="type">执行类</param>
        /// <param name="methodname">执行方法</param>
        /// <param name="msg">消息</param>
        /// <returns></returns>
        bool InvokeMethod(Type type, MethodInfo method, string msg)
        {
            if (method != null)
            {
                try
                {
                    var paras = new List<object>();//方法需调用的参数

                    var methodParas = method.GetParameters();
                    if (methodParas.Any())
                        paras.Add(JsonConvert.DeserializeObject(msg, methodParas?.First()?.ParameterType));

                    var Constructors = type.GetConstructors();
                    var param = new List<object>();
                    foreach (var item in Constructors)
                    {
                        var types = item.GetParameters();
                        if (types.Length > 0)
                        {
                            foreach (var t in types)
                            {
                                param.Add(_serviceProvider.GetService(t.ParameterType));
                            }
                        }
                    }
                    var res = method.Invoke(Activator.CreateInstance(type, param.ToArray()), paras.ToArray());
                    return Convert.ToBoolean(res);
                }
                catch (Exception ex)
                {

                    throw;
                }
            }
            return false;
        }
    }
}
